package network;

import data.Building;
import data.Village;
import com.thoughtworks.xstream.XStream;
import data.*;

import java.awt.Point;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Vector;
import java.util.Vector;
import maiaclient.Maia;

/**
 *
 * @author groep 11
 * @date 2007-2008
 */
public class MaiaClient {

    private Socket link;
    private ObjectInputStream in;
    private ObjectOutputStream out;
    private Maia maia; 
    private Village village;
    private User user;
    private Vector<Village> villages;
    private Vector<User> onlineUsers;
    private Vector<User> friends;
    private Vector<User> tribeMembers;

    public MaiaClient(Maia maia) {
        this.maia = maia;
    }

    // <editor-fold desc="GETTERS/SETTERS/RESETS" defaultstate="collapsed">
    /**
     * Fetches a list with the namesOnlineUsers of all online users.
     * @return namesOnlineUsers
     */
    public Vector<User> getOnlineUsers() {
        return this.onlineUsers;
    }

    /**
     * Fetches a list with the friends of all online users.
     * @return friends
     */
    public Vector<User> getFriends() {
        return this.friends;
    }

    /**
     * Fetches a list with the tribeMembers of all online users.
     * @return tribeMembers
     */
    public Vector<User> getTribeMembers() {
        return this.tribeMembers;
    }

    /*
     * Setter of the village field. 
     * Should be called from the AcceptMessageHandler.
     */
    private void setVillage(String xml) {
        XStream xstream = new XStream();
        this.village = (Village) xstream.fromXML(xml);
    }

    /*
     * Setter of the user field.
     * Should be called from the AcceptMessageHandler
     */
    private void setUser(String xml) {
        XStream xstream = new XStream();
        this.user = (User) xstream.fromXML(xml);
    }

    /*
     * Setter of the namesOnlineUsers field.
     * Should be called from the AcceptMessageHandler
     */
    private void setOnlineUsers(String xml) {
        XStream xstream = new XStream();
        this.onlineUsers = (Vector<User>) xstream.fromXML(xml);

        maia.updateChatListeners();

//        //+
//        System.out.println("Online users: ");
//        for (User u : onlineUsers) {
//            System.out.println(u.toString());
//        }
//        System.out.println("End of list.");
//    //+
    }

    /*
     * Setter of the friends field.
     * Should be called from the AcceptMessageHandler
     */
    private void setFriends(String xml) {
        XStream xstream = new XStream();
        this.friends = (Vector<User>) xstream.fromXML(xml);

//        //+
//        System.out.println("Friends: ");
//        for (User u : friends) {
//            System.out.println(u.toString());
//        }
//        System.out.println("End of list.");
//    //+
    }

    /*
     * Setter of the tribeMembers field.
     * Should be called from the AcceptMessageHandler
     */
    private void setTribeMembers(String xml) {
        XStream xstream = new XStream();
        this.tribeMembers = (Vector<User>) xstream.fromXML(xml);

//        //+
//        System.out.println("Tribe members: ");
//        for (User u : tribeMembers) {
//            System.out.println(u.toString());
//        }
//        System.out.println("End of list.");
//    //+
    }

    /**
     * Resets the village field.
     */
    public void resetVillage() {
        this.village = null;
    }

    /**
     * Resets the villages field.
     */
    public void resetVillages() {
        this.villages = null;
    }

    /**
     * Resets the user field.
     */
    public void resetUser() {
        this.user = null;
    }

    // </editor-fold>
    // <editor-fold desc="CONNECT/DISCONNECT" defaultstate="collapsed">
    /**
     * Sets up a connection between the client and a given server address.
     * @param serverAddress
     * @param serverPort
     * @throws java.io.IOException
     */
    public void connectTo(InetAddress serverAddress, int serverPort) throws IOException {
        if (link == null) {
            InetAddress host = serverAddress;
            int port = serverPort;
            link = new Socket(host, port);

            out = new ObjectOutputStream(link.getOutputStream());
            in = new ObjectInputStream(link.getInputStream());

            System.out.println("Connection...ok!");
        }
    }

    /**
     * Sets up a connection between the client and a given server ip.
     * @param serverIP
     * @param serverPort
     * @throws java.io.IOException
     */
    public void connectTo(String serverIP, int serverPort) throws IOException {
        connectTo(InetAddress.getByName(serverIP), serverPort);
    }

    public void connect() throws IOException {
        connectTo(InetAddress.getByName(maia.getServerIP()), maia.getServerPort());
    }

    /**
     * Returns the status of the connection.
     * @return <CODE>true</CODE> if link is up
     * <CODE>false</CODE> iif link is down.
     */
    public boolean getLinkStatus() {
        if (link.isClosed() || link == null) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * Closes the connection with the server.
     * @throws java.io.IOException 
     */
    public void closeConnection() throws IOException {
        try {
            if (link != null) {
                sendToServer("#CONNTERM#");

                out.close();
                in.close();
                link.close();
                link = null; //necessary to be able to setup a new connection later on

                System.out.println("Client terminated the connection."); // DEBUG
            }

        } catch (IOException ex) {
            System.err.println("Error during disconnect!");
            throw ex;
        }
    }

    public void closeConnectionOut() throws IOException {
        try {
            if (link != null) {
                sendToServer("#CONNTERM#");

                out.close();
                System.out.println("Client terminated the connection."); // DEBUG
            }

        } catch (IOException ex) {
            System.err.println("Error during disconnect!");
            throw ex;
        }
    }

    // </editor-fold>
    // <editor-fold desc="GENERAL COMMUNICATION" defaultstate="collapsed">
    private synchronized void sendToServer(Object obj) throws IOException {
        out.writeObject(obj);
        out.flush();
    }
    // <editor-fold desc="With return" defaultstate="collapsed">
    /**
     * Logs in a user.
     * The login credentials are sent to the server. If successful a user object is returned, else null.
     * @param username
     * @param password
     * @return user object of logged in user
     * @throws java.io.IOException
     * @throws java.lang.ClassNotFoundException
     */
    public User loginUser(String username, String password) throws IOException, ClassNotFoundException {

        XStream xstream = new XStream();

        // outgoing data
        sendToServer("#LOGIN#" + username + ";" + password);

        // icomming data
        String xml = (String) in.readObject();
        User User = (User) xstream.fromXML(xml);

        return User;
    }

    public boolean checkAvailableUsername(String sUsername) throws IOException {

        sendToServer("#CHECKAVAILUSR#" + sUsername);

        boolean result = in.readBoolean();

        return result;
    }

    public boolean checkAvailableVillage(String sVillageName) throws IOException {

        sendToServer("#CHECKAVAILVILL#" + sVillageName);

        boolean result = in.readBoolean();

        return result;
    }

    /**
     * Fetches the village associated with a given username.
     * @param username
     * @return village 
     * @throws java.io.IOException
     */
    public Village fetchVillageOfUser(String username) throws IOException, ClassNotFoundException {


        sendToServer("#QRYDORP#" + username);

        String incomming = (String) in.readObject();

        // get only the real object and not the command
        String xmlObject = incomming.substring(incomming.indexOf("#", 2) + 1);

        XStream xstream = new XStream();
        Village localVillage = (Village) xstream.fromXML(xmlObject);

        return localVillage;
    }

    /**
     * Fetches a user by a given username.
     * Returns null if the user was not found.
     * 
     * @param username
     * @return user
     * @throws java.io.IOException
     * @throws ClassNotFoundException 
     */
    public User fetchUserByUsername(String username) throws IOException, ClassNotFoundException {

        sendToServer("#QRYUSRBYNAME#" + username);

        String incomming = (String) in.readObject();

        // get only the real object and not the command
        String xmlObject = incomming.substring(incomming.indexOf("#", 2) + 1);

        XStream xstream = new XStream();
        User localUser = (User) xstream.fromXML(xmlObject);

        return localUser;
    }

    /**
     * Fetches all known villages.
     * @return villages
     * @throws java.io.IOException
     * @throws java.lang.ClassNotFoundException
     */
    public Vector<Village> fetchInitializedVillages() throws IOException, ClassNotFoundException {
        sendToServer("#QRYVILLAGES#");

        String incomming = (String) in.readObject();

        // get only the real object and not the command
        String xmlObject = incomming.substring(incomming.indexOf("#", 2) + 1);

        XStream xstream = new XStream();
        Vector<Village> localVillages = (Vector<Village>) xstream.fromXML(xmlObject);

        return localVillages;
    }

    public User fetchUserByUserId(int userId) throws IOException, ClassNotFoundException {

        sendToServer("#QRYUSERBYID#" + userId);

        String incomming = (String) in.readObject();

        // get only the real object and not the command
        String xmlObject = incomming.substring(incomming.indexOf("#", 2) + 1);

        XStream xstream = new XStream();
        User localUser = (User) xstream.fromXML(xmlObject);

        return localUser;
    }

    public Vector<BuildingType> fetchBuildingTypes() throws IOException, ClassNotFoundException {

        sendToServer("#QRYBUILDINGTYPES#");

        String incomming = (String) in.readObject();

        // get only the real object and not the command
        String xmlObject = incomming.substring(incomming.indexOf("#", 2) + 1);

        XStream xstream = new XStream();
        Vector<BuildingType> buildingTypes = (Vector<BuildingType>) xstream.fromXML(xmlObject);

        return buildingTypes;
    }
    // </editor-fold>
    // <editor-fold desc="Void" defaultstate="collapsed">
    public void startAcceptMessageHandler() {
        // create listener
        AcceptMessageHandler listener = new AcceptMessageHandler();
        Thread t = new Thread(listener);
        t.start();
    }

    /**
     * Sends a new user to the server.
     * @param newUser
     * @throws java.io.IOException
     */
    public void sendNewUser(User newUser) throws IOException {

        XStream xstream = new XStream();

        // outgoing data
        String xml = xstream.toXML(newUser);
        sendToServer("#USRADD#" + xml);

    }

    /**
     * Sends a new village to the server.
     * @param village
     * @throws java.io.IOException
     */
    public void sendNewVillage(Village village) throws IOException {

        XStream xstream = new XStream();

        String xml = xstream.toXML(village);
        sendToServer("#ADDVILLAGE#" + xml);
    }

    public void signInToWorld() throws IOException {
        XStream xstream = new XStream();

        String xml = xstream.toXML(maia.getUser());
        sendToServer("#WORLDIN#" + xml);
    }

    public void updateVillageLocation(Point location, int villageId) throws IOException {
        sendToServer("#UPDATEVILLAGELOCATION#" + villageId + ";" + location.x + ";" + location.y);
    }

    public void assignDefaultResources(int villageId) throws IOException {
        sendToServer("#ASSIGNDEFAULTRESOURCES#" + villageId);
    }

    public void addBuilding(Building building) throws IOException {
        XStream xstream = new XStream();

        String xml = xstream.toXML(building);
        sendToServer("#ADDBUILDING#" + xml);
    }

    public void requestInitializedVillages() throws IOException, ClassNotFoundException {
        sendToServer("#QRYVILLAGES#");
    }

    public void requestUserByUserid(int userId) throws IOException {
        sendToServer("#QRYUSERBYID#" + userId);
    }

    public void requestFriends(int userId) throws IOException {
        sendToServer("#QRYFRIENDS#" + userId);

    }

    public void requestResources() throws IOException {
        sendToServer("#QRYRESOURCES#");
    }

    public void requestBuildingTypes() throws IOException {
        sendToServer("#QRYBUILDINGTYPES#");
    }

    public void requestBuildings() throws IOException {
        sendToServer("#QRYBUILDINGS#");
    }

    public void receiveInitializedVillages(String xml) {

        XStream xstream = new XStream();
        Vector<Village> localVillages = (Vector<Village>) xstream.fromXML(xml);
        maia.showVillages(localVillages);
    }

    public void receiveUserByUserId(String xml) {

        XStream xstream = new XStream();
        User localUser = (User) xstream.fromXML(xml);
        maia.showUserInfo(localUser);

    }

    public void receiveFriendsList(String xml) {

        XStream xstream = new XStream();
        Vector<User> friendslst = (Vector<User>) xstream.fromXML(xml);
        maia.showFriends(friendslst);

    }

    public void receiveResources(String xml) {

        XStream xstream = new XStream();
        Vector<Resource> resources = (Vector<Resource>) xstream.fromXML(xml);
        maia.updateResources(resources);
    }

    public void receiveBuildings(String xml) {

        XStream xstream = new XStream();
        Vector<Building> resources = (Vector<Building>) xstream.fromXML(xml);
        maia.updateBuildings(resources);
    }

    public void receiveBuildingTypes(String xml) {

        XStream xstream = new XStream();
        Vector<BuildingType> types = (Vector<BuildingType>) xstream.fromXML(xml);
        maia.updateBuildingTypes(types);
    }

    public void invokeChatListUpdate() throws IOException {
        sendToServer("#CHATLISTS#");
    }

    public void destructBuilding(int buildingId) throws IOException {

        sendToServer("#REMOVEBUILDING#" + buildingId);

    }

    public void levelUpBuilding(Building building) throws IOException {
        XStream xstream = new XStream();

        String xml = xstream.toXML(building);
        sendToServer("#UPDATEBUILDING#"+ xml);
    }
    public void updateResources(Vector<Resource> resources) throws IOException{
        XStream xstream = new XStream();
        
        String xml  = xstream.toXML(resources);
        sendToServer("#UPDATERESOURCES#"+ xml);
    }
    //</editor-fold>
    // </editor-fold>
    // <editor-fold desc="CHATTEN" defaultstate="collapsed">
    /**
     * Sends out a chat message to all of the user's friends.
     * @param message
     * @throws java.io.IOException 
     */
    public void sendFriendsChatmessage(String message) throws IOException {
        sendToServer("#CHATF#" + message);
    }

    /**
     * Sends out a chat message to all of the user's tribe members. 
     * @param message
     * @throws java.io.IOException 
     */
    public void sendTribeChatmessage(String message) throws IOException {
        sendToServer("#CHATT#" + message);
    }

    /**
     * Sends out a chat message to all online users.
     * @param message
     * @throws java.io.IOException 
     */
    public void sendAllChatmessage(String message) throws IOException {
        sendToServer("#CHATA#" + message);
    }

    /*
     * Processes an incomming friends chat message.
     */
    private void receiveFriendsChatMessage(String message) {
        maia.showChatMessage(message, 2);
    }

    /*
     * Processes an incomming tribe chat message.
     */
    private void receiveTribeChatMessage(String message) {
        maia.showChatMessage(message, 1);
    }

    /*
     * Processes an incomming all users chat message
     */
    private void receiveAllChatMessage(String message) {
        maia.showChatMessage(message, 0);
    }


    // </editor-fold>
    //<editor-fold desc="LISTENER (AcceptMessageHandler)" defaultstate="collapsed">
    /*
     * Innerclass listening to incomming messages
     */
    private class AcceptMessageHandler implements Runnable {

        public void run() {
            try {
                String incomming = null;
                String command = null;
                System.out.println("AcceptmessageHandler running!");
                do {
                    incomming = (String) in.readObject();

                    // splits the incomming string at the second "#"
                    command = incomming.substring(0, incomming.indexOf("#", 2) + 1);
                    final String params = incomming.substring(incomming.indexOf("#", 2) + 1);

                    if (command.equals("#_QRYDORP#")) {
                        setVillage(params);

                    } else if (command.equals("#_QRYUSRBYNAME#")) {
                        setUser(params);

                    } else if (command.equals("#_QRYVILLAGES#")) {
                        Thread t = new Thread(new Runnable() {

                            public void run() {
                                receiveInitializedVillages(params);
                            }
                        });
                        t.start();

                    } else if (command.equals("#_QRYONLINEUSERS#")) {
                        Thread t = new Thread(new Runnable() {

                            public void run() {
                                setOnlineUsers(params);
                            }
                        });
                        t.start();

                    } else if (command.equals("#_QRYBUILDINGTYPES#")) {
                        Thread t = new Thread(new Runnable() {

                            public void run() {
                                receiveBuildingTypes(params);
                            }
                        });
                        t.start();

                    } else if (command.equals("#_QRYONLINEUSERS#")) {
                        Thread t = new Thread(new Runnable() {

                            public void run() {
                                setOnlineUsers(params);
                            }
                        });
                        t.start();

                    } else if (command.equals("#_QRYUSERBYID#")) {
                        Thread t = new Thread(new Runnable() {

                            public void run() {
                                receiveUserByUserId(params);
                            }
                        });
                        t.start();

                    } else if (command.equals("#_QRYRESOURCES#")) {
                        Thread t = new Thread(new Runnable() {

                            public void run() {
                                receiveResources(params);
                            }
                        });
                        t.start();

                    } else if (command.equals("#_QRYBUILDINGS#")) {
                        Thread t = new Thread(new Runnable() {

                            public void run() {
                                receiveBuildings(params);
                            }
                        });
                        t.start();

                    } else if (command.equals("#_QRYFRIENDS#")) {
                        Thread t = new Thread(new Runnable() {

                            public void run() {
                                receiveFriendsList(params);
                                setFriends(params);

                            }
                        });
                        t.start();
                    } else if (command.equals("#_QRYTRIBEMEMBERS#")) {
                        setTribeMembers(params);

                    } else if (command.equals("#_CHATF#")) {
                        Thread t = new Thread(new Runnable() {

                            public void run() {
                                receiveFriendsChatMessage(params);
                            }
                        });
                        t.start();


                    } else if (command.equals("#_CHATT#")) {
                        Thread t = new Thread(new Runnable() {

                            public void run() {
                                receiveTribeChatMessage(params);
                            }
                        });
                        t.start();

                    } else if (command.equals("#_CHATA#")) {
                        Thread t = new Thread(new Runnable() {

                            public void run() {
                                receiveAllChatMessage(params);
                            }
                        });
                        t.start();
                    }
                } while (true);

            } catch (IOException ex) {
                System.err.println(ex.getMessage());
            } catch (ClassNotFoundException ex) {
                System.err.println(ex.getMessage());
            }
        }
    }
    // </editor-fold>
}
